﻿using MongoDB.Driver;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Synchronization.Interface;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Sustainalytics.SecurityListsSync
{
    public class SyncHBaseSecurityLists : ISyncStep
    {
        private readonly CDDB.DataConnector.SecurityListRepository _cddb = new CDDB.DataConnector.SecurityListRepository();

        private SyncContext _syncContext;
        private ILogger _logger;

        public SyncStepOrder ExecutionOrder => SyncStepOrder.SyncSecuritiesLists;

        private readonly TimeSpan _hBaseWaitRetryDuration = TimeSpan.FromSeconds(int.Parse(ConfigurationManager.AppSettings["HBaseWaitRetryDuration"]));
        private readonly int _hBaseRetryNumberOfTimes = int.Parse(ConfigurationManager.AppSettings["HBaseRetryNumberOfTimes"]);

        public SyncContext Start(SyncContext syncContext)
        {
            _syncContext = syncContext;
            _logger = new SyncContextLogger(syncContext);

            var syncDsoData = bool.Parse(ConfigurationManager.AppSettings["SyncDsoDatabase"]);
            if (syncDsoData)
            {
                SyncSecurityListsAsync(_syncContext.DSOConnectionStrings["ScreeningDW"]).Wait();
                SetSecurityListIdsInCompanyProfileResearchStateAsync(_syncContext.GAConnectionStrings["ClientDW"]).Wait();
            }

            return _syncContext;
        }

        private async Task SyncSecurityListsAsync(string connectionString)
        {
            var securitiesListCollection = MongoFactory.GetCollectionFromDatabase<SecurityList>(connectionString);
            await securitiesListCollection.DeleteManyAsync(Builders<SecurityList>.Filter.Empty).ConfigureAwait(false);

            var securityLists = LogExecutionTime("Get Security Lists", () => Retry.Do(() =>
               _cddb.GetSecurityLists().Select(x => new SecurityList(x.Id, x.Name)).ToList(), _hBaseWaitRetryDuration, _logger, _hBaseRetryNumberOfTimes));

            await InsertSecurityListAsync(securitiesListCollection, securityLists).ConfigureAwait(false);
            await InsertSecurityListsCompanyIdsAsync(securitiesListCollection, securityLists).ConfigureAwait(false);
        }

        private async Task InsertSecurityListAsync(IMongoCollection<SecurityList> securitiesListCollection, List<SecurityList> securityLists)
        {
            await LogExecutionTimeAsync("Insert Security List", async () =>
            {
                await securitiesListCollection.InsertManyAsync(securityLists).ConfigureAwait(false);
            }).ConfigureAwait(false);
        }

        private async Task InsertSecurityListsCompanyIdsAsync(IMongoCollection<SecurityList> securitiesListCollection, IEnumerable<SecurityList> securityLists)
        {
            await LogExecutionTimeAsync("Insert Security List CompanyIds", async () =>
            {
                foreach (var securityList in securityLists)
                {
                    var companyIds = Retry.Do(() => _cddb.GetSecurityListConstituents(securityList.Id)
                        .Select(x => x.CompanyIQID), _hBaseWaitRetryDuration, _logger, _hBaseRetryNumberOfTimes);
                    var updateDefinition = Builders<SecurityList>.Update.AddToSetEach(s => s.CompanyIds, companyIds);
                    await securitiesListCollection.UpdateOneAsync(x => x.Id == securityList.Id, updateDefinition).ConfigureAwait(false);
                }
            });
        }

        private async Task SetSecurityListIdsInCompanyProfileResearchStateAsync(string connectionString)
        {
            await LogExecutionTimeAsync("Set SecurityListIds in CompanyProfileResearchState", async () =>
            {
                var companyProfileResearchStateCollection = MongoFactory.GetCollectionFromDatabase<CompanyProfileResearchState>(connectionString);
                var groups = Retry.Do(() => _cddb.GetSecuritiesAndSecuritiesListAssociations().GroupBy(x => x.CompanyIQID), _hBaseWaitRetryDuration, _logger, _hBaseRetryNumberOfTimes);
                foreach (var group in groups)
                {
                    var capitalIqId = @group.Key;
                    var securityListsIds = @group.SelectMany(x => x.SecurityListsIds).ToList();
                    var updateDefinition = Builders<CompanyProfileResearchState>.Update.Set(x => x.SecurityListIds, securityListsIds);
                    var updateResult = await companyProfileResearchStateCollection.UpdateOneAsync(x => x.CapitalIqId == capitalIqId, updateDefinition).ConfigureAwait(false);
                    if (updateResult.ModifiedCount == 0)
                    {
                        _syncContext.Log("Missing CapitalIQID: " + capitalIqId);
                    }
                }
            }).ConfigureAwait(false);
        }

        private T LogExecutionTime<T>(string message, Func<T> action)
        {
            _syncContext.Log("START - " + message);
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            var result = action.Invoke();
            stopwatch.Stop();
            _syncContext.Log("FINISHED - " + message + " in " + stopwatch.Elapsed);

            return result;
        }

        private async Task LogExecutionTimeAsync(string message, Func<Task> action)
        {
            _syncContext.Log("START - " + message);
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            await action.Invoke();
            stopwatch.Stop();
            _syncContext.Log("FINISHED - " + message + " in " + stopwatch.Elapsed);
        }
    }
}
